﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using CommandLine;
using Vroom.Bootstrap;
using Vroom.Common;
using Vroom.Common.Config;
using Vroom.Common.Enums;
using Vroom.Common.Exceptions;
using Vroom.Common.IFaces.Exceptions;
using Vroom.Common.IFaces.Loader;
using Vroom.Config;
using Vroom.Loaders.Types;
using log4net;
using log4net.Appender;
using log4net.Repository;

namespace Vroom
{    
    [GuidAttribute("D176B529-44A9-4DB9-897A-EC698BB74BFA")]
    public class LoaderBootstrap : ILoaderBootstrap
    {

        #region Properties
        
        IConfigFetcher           ConfigFetcher;
        public Guid              ClassGuid        { get { return GetType().GUID; } }
        public Guid              ExecGuid         { get; set; }
        public IExceptionContext ExceptionContext { get; set; }
        public SourceSystem      SourceSystem     { get; set; }

        private static readonly ILog logger = log4net.LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType.Name); 

        #endregion
        
        
        public static void Main(string[] args)
        {
            ILoaderBootstrap loaderBootstrap = new LoaderBootstrap();
            loaderBootstrap.ParseArgs(args);
            loaderBootstrap.Start();
        }


        public void ParseArgs(string[] args)
        {            
            IInputArgs inputArgs = new InputArgs();
            if (!Parser.Default.ParseArguments(args, inputArgs))
            {
                logger.Error("Vroom called incorrectly, CommandLine: " + String.Join(" ", Environment.GetCommandLineArgs().Select(a => String.Join(" ", a))));
                Environment.Exit(-1);
            }
            else
            {
                SourceSystem = inputArgs.SourceSystem;
            }
        }


        public void Start()
        {
            try
            {
                SetUp();
                BeginLoading();
                TearDown();
            }
            catch (Exception e)
            {
                logger.Error(e.Message, e);
                ExceptionContext.Exceptions.Add(e);
            }
        }
        

        public void BeginLoading()
        {
            ParallelLoopResult loopResult;
            CancellationTokenSource cts= new CancellationTokenSource();
            ParallelOptions paraOptions = new ParallelOptions();
            paraOptions.CancellationToken = cts.Token;
            paraOptions.MaxDegreeOfParallelism = System.Environment.ProcessorCount;

            try
            {              
                // Fetch Config
                ConfigFetcher = new ConfigFetcher();

                // Traverse All ConfigItems where are set as enabled
                IEnumerable<IConfigItem> configItems = ConfigFetcher.GetItemsToLoad().Where(p => p.IsEnabled == 1 && p.SourceSystem == SourceSystem);

                // Process Items in Parallel
                if (configItems.Count() > 0)
                {
                    logger.Info("Processing for SourceSystem: " + SourceSystem + ", Num of Itmes: " + configItems.Count());
                    loopResult = Parallel.ForEach(configItems, conf => ProcessItem(conf, paraOptions));
                }
                else
                {
                    //string message = "No enabled Items for SourceSystem: " + SourceSystem + ", Exiting now...";
                    //logger.Warn("No enabled Items for SourceSystem: " + SourceSystem + ", Exiting now...");
                    //ExceptionContext.Exceptions.Add(new ConfigException("No enabled Items for SourceSystem: " + SourceSystem + ", Exiting now..."));
                    throw new ConfigException("No enabled Items for SourceSystem: " + SourceSystem + ", Exiting now...");
                }
            }
            catch (AggregateException aggEx)
            {
                foreach (var ex in aggEx.InnerExceptions) { ExceptionContext.Exceptions.Add(ex); }
                cts.Cancel();
            }            
        }


        public void ProcessItem(IConfigItem item, ParallelOptions paraOptions)
        {
            try
            {
                if (item.DestinationType == DestinationType.MSSQL)
                {
                    IDbLoader loader = new SqlServerLoader(item, ExecGuid);
                    loader.BeginLoad();
                    paraOptions.CancellationToken.ThrowIfCancellationRequested();
                }
            }
            catch (Exception e)
            {
                ExceptionContext.Exceptions.Add(e);
            }
        }


        private void SetUp()
        {
            ExecGuid = Guid.NewGuid();
            ExceptionContext = new ExceptionContext(ExecGuid);
            ThreadContext.Properties["ExecGuid"] = ExecGuid.ToString();
            ThreadContext.Properties["ClassGuid"] = ClassGuid.ToString();
            logger.Info("Starting...");
        }


        private void TearDown()
        {
            if (ExceptionContext.Exceptions.Count > 0)
            {
                foreach (var err in ExceptionContext.Exceptions)
                {
                    logger.Error(err.Message, err);
                }

                FlushLoggers();
            }
            else
            {
                ThreadContext.Properties["ClassGuid"] = ClassGuid.ToString();
                logger.Info("Stopping...");
            }


            if (ExceptionContext.Exceptions.Count > 0)
                Environment.Exit(-1);
        }


        public void FlushLoggers()
        {
            ILoggerRepository rep = LogManager.GetRepository();
            foreach (IAppender appender in rep.GetAppenders())
            {
                var buffered = appender as BufferingAppenderSkeleton;
                if (buffered != null)
                {
                    ThreadContext.Properties["ExecGuid"]  = ExecGuid.ToString();
                    ThreadContext.Properties["ClassGuid"] = ClassGuid.ToString();
                    buffered.Flush();
                }
            }
        }

    }
}